﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;

namespace com.lover.common.http
{
    public class HttpSocket
    {
        private int _read_block_size = 10240; // socket查询状态过期时间(单位:ms)
        private string _url;
        /// <summary>
        /// url
        /// </summary>
        public string Url
        {
            get { return _url; }
            set
            {
                _url = value;
                if (_url == null || _url.Length == 0) return;
                _uri = new Uri(_url);
            }
        }
        private Uri _uri;
        private string _method = "GET";
        /// <summary>
        /// method GET | POST
        /// </summary>
        public string Method
        {
            get { return _method; }
            set
            {
                if(value.Equals("GET") || value.Equals("POST"))
                    _method = value;
            }
        }
        private string _data;
        /// <summary>
        /// data to be posted
        /// </summary>
        public string Data
        {
            get { return _data; }
            set { _data = value; }
        }
        private string _userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0";
        /// <summary>
        /// user agent
        /// </summary>
        public string UserAgent
        {
            get { return _userAgent; }
            set { _userAgent = value; }
        }
        private string _referer;
        /// <summary>
        /// referer
        /// </summary>
        public string Referer
        {
            get { return _referer; }
            set { _referer = value; }
        }
        private Encoding _encoding = Encoding.UTF8;
        /// <summary>
        /// 编码
        /// </summary>
        public Encoding Encoding
        {
            get { return _encoding; }
            set { _encoding = value; }
        }

        private int _sendTimeout = 30;
        /// <summary>
        /// 单位:秒
        /// </summary>
        public int SendTimeout
        {
            get { return _sendTimeout; }
            set { _sendTimeout = value; }
        }
        private int _recvTimeout = 30;
        /// <summary>
        /// 单位:秒
        /// </summary>
        public int RecvTimeout
        {
            get { return _recvTimeout; }
            set { _recvTimeout = value; }
        }
        private Dictionary<string, string> _extraHeaders = new Dictionary<string,string>();
        public Dictionary<string, string> getExtraHeaders() { return _extraHeaders; }
        public void addExtraHeader(string key, string value)
        {
            if (_extraHeaders.ContainsKey(key)) _extraHeaders[key] = value;
            else _extraHeaders.Add(key, value);
        }
        private List<Cookie> _cookies;
        public void setCookies(List<Cookie> cookies) { _cookies = cookies; }
        private HttpResult _result = new HttpResult();
        public HttpResult execute()
        {
            if (_uri == null) return null;
            _result.CurrentUri = _uri;
            if (_uri.Scheme.Equals("http"))
            {
                executeHttp();
            }
            else if(_uri.Scheme.Equals("https"))
            {
                executeHttps();
            }
            return _result;
        }
        private void executeHttp()
        {
            Socket sock = null;
            try
            {
                sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                sock.ReceiveTimeout = _recvTimeout * 1000;
                sock.SendTimeout = _sendTimeout * 1000;
                sock.Connect(_uri.Host, _uri.Port);
                if (!sock.Connected) return;

                string headerString = getHeaderString();
                byte[] header_bytes = _encoding.GetBytes(headerString);
                sock.Send(header_bytes, SocketFlags.None);

                if (_method.Equals("POST"))
                {
                    byte[] data_bytes = _encoding.GetBytes(_data);
                    sock.Send(data_bytes, SocketFlags.None);
                }

                // begin receive
                MemoryStream recv_stream = new MemoryStream();
                int stream_length = 0;
                int body_start = 0;

                int read_length = 0;
                // first read header, header cannot longer than 10240
                int block_size = 10240;
                byte[] recv = new byte[block_size];
                // 这里需要设置一个延时来保证socket的数据已经可以读取, 不需要了??
                
                read_length = sock.Receive(recv, 0, block_size, SocketFlags.None);

                recv_stream.Write(recv, 0, read_length);
                stream_length += read_length;
                recv_stream.Seek(0, SeekOrigin.Begin);

                processHeader(recv_stream, stream_length, ref body_start);

                MemoryStream body_stream = new MemoryStream();
                body_stream.Seek(0, SeekOrigin.Begin);
                int body_now_length = stream_length - body_start;

                if (_result.Chunked)
                {
                    getChunkedData(ref sock, ref recv_stream, ref body_stream, ref body_start, stream_length);
                }
                else
                {
                    if (stream_length - body_start > 0)
                    {
                        // 先把recv_stream里面已经读取到的数据写入body_stream
                        recv_stream.Seek(body_start, SeekOrigin.Begin);
                        byte[] tmpbuffer = new byte[stream_length - body_start];
                        recv_stream.Read(tmpbuffer, 0, tmpbuffer.Length);
                        body_stream.Write(tmpbuffer, 0, tmpbuffer.Length);
                    }

                    // 已经知道数据长度
                    if (_result.ContentLength >= 0)
                    {
                        if (_result.ContentLength > body_now_length)
                        {
                            body_stream.Seek(0, SeekOrigin.End);
                            readSocketData(sock, ref body_stream, ref body_now_length, _result.ContentLength);
                        }
                    }
                    else
                    {
                        // 如果接收到的数据是满的(说明可能还有额外的数据) 或者 接收到的数据只有header
                        if (read_length >= block_size || (_result.StatusCode == 200 && body_start == stream_length))
                        {
                            body_stream.Seek(0, SeekOrigin.End);
                            readSocketData(sock, ref body_stream, ref body_now_length);
                        }
                    }
                }
                sock.Close();
                sock = null;

                processBody(body_stream, body_now_length);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error::" + ex.StackTrace);
            }
            finally
            {
                if (sock != null)
                {
                    sock.Close();
                    sock = null;
                }
            }
        }
        private int getChunkedHead(ref byte[] tmpbuffer, ref int start_index)
        {
            List<char> tmpchars = new List<char>();
            int chunk_size = -1;

            for (int j = 0, len_j = tmpbuffer.Length; j < len_j; j++)
            {
                char tmpc = (char)tmpbuffer[j];
                if (tmpc == '\n')
                {
                    try
                    {
                        string tmpstring = new string(tmpchars.ToArray()).TrimEnd('\r');
                        if (tmpstring != null && tmpstring.Length > 0)
                        {
                            chunk_size = Convert.ToInt32(tmpstring, 16);
                        }
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Maybe exists 'chunk-ext' field." + e.StackTrace);
                    }
                    finally
                    {
                        start_index += j + 1;
                    }

                    break;
                }
                tmpchars.Add(tmpc);
            }
            return chunk_size;
        }
        private void getChunkedBody<T>(ref T sock, ref MemoryStream data_stream, ref MemoryStream body_stream,
            ref int chunk_body_start, ref int chunk_size, ref int data_stream_length)
        {
            if (data_stream_length < chunk_body_start + chunk_size)// 如果数据不够这个chunk
            {
                data_stream.Seek(0, SeekOrigin.End);
                while (data_stream_length < chunk_body_start + chunk_size)
                {
                    byte[] tmpbuffer = new byte[1024];
                    int readed = readData(ref sock, ref tmpbuffer);
                    if (readed == 0) break;
                    data_stream.Write(tmpbuffer, 0, readed);
                    data_stream_length += readed;
                }
            }
            if (data_stream_length >= chunk_body_start + chunk_size)// 如果数据足够这个chunk
            {
                data_stream.Seek(chunk_body_start, SeekOrigin.Begin);
                byte[] tmpbuffer = new byte[chunk_size];
                data_stream.Read(tmpbuffer, 0, chunk_size);
                body_stream.Write(tmpbuffer, 0, chunk_size);
                chunk_body_start += chunk_size;
            }
        }
        private void getChunkedData<T>(ref T sock, ref MemoryStream data_stream, ref MemoryStream body_stream, ref int body_start, int stream_length)
        {
            do 
            {
                data_stream.Seek(body_start, SeekOrigin.Begin);
                byte[] tmpbuffer = new byte[1024];
                int readed = data_stream.Read(tmpbuffer, 0, tmpbuffer.Length);
                if (readed == 0)
                {
                    readed = readData(ref sock, ref tmpbuffer);
                    if (readed == 0) break;
                    data_stream.Seek(0, SeekOrigin.End);
                    data_stream.Write(tmpbuffer, 0, readed);
                    stream_length += readed;
                    continue;
                }

                int chunk_size = getChunkedHead(ref tmpbuffer, ref body_start);
                if (chunk_size < 0) continue; // 可能是因为一个纯粹的\r\n, 继续
                if (chunk_size == 0) break; // 已经是最后一个chunk, 没有下一个了


                getChunkedBody(ref sock, ref data_stream, ref body_stream, ref body_start, ref chunk_size, ref stream_length);
            } while (true);
        }
        private int readData<T>(ref T sock, ref byte[] buffer)
        {
            if (sock is Socket)
                return (sock as Socket).Receive(buffer, 0, buffer.Length, SocketFlags.None);
            else if (sock is SslStream)
                return (sock as SslStream).Read(buffer, 0, buffer.Length);
            return 0;
        }
        private void readSocketData<T>(T sock, ref MemoryStream recv_stream, ref int stream_length, int target_stream_length=0)
        {
            int read_length = 0;
            byte[] recv;
            do
            {
                recv = new byte[_read_block_size];
                try
                {
                    if(sock is Socket)
                        read_length = (sock as Socket).Receive(recv, 0, _read_block_size, SocketFlags.None);
                    else if(sock is SslStream)
                        read_length = (sock as SslStream).Read(recv, 0, _read_block_size);
                }
                catch (System.Exception ex)
                {
                    read_length = 0;
                    Console.WriteLine(String.Format("ERROR::{0}::{1}", ex.Message, ex.StackTrace));
                }
                recv_stream.Write(recv, 0, read_length);
                stream_length += read_length;

                if (read_length == 0 || (target_stream_length > 0 && target_stream_length <= stream_length)) break;

                Console.WriteLine("data read: " + read_length);
                Console.WriteLine("now length: " + stream_length);
            } while (read_length > 0);
        }
        private void executeHttps()
        {
            StringBuilder sb = new StringBuilder(1024);

            string headerString = getHeaderString();
            byte[] header_bytes = _encoding.GetBytes(headerString);

            TcpClient client = null;
            try
            {
                client = new TcpClient(_uri.Host, _uri.Port);
                SslStream sslStream = new SslStream(client.GetStream(), true
                    , new RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors)
                        =>
                    {
                        return sslPolicyErrors == SslPolicyErrors.None;
                    }
                        ), null);
                sslStream.ReadTimeout = _recvTimeout * 1000;
                sslStream.WriteTimeout = _sendTimeout * 1000;

                X509Store store = new X509Store(StoreName.My);

                sslStream.AuthenticateAsClient(_uri.Host, store.Certificates,
                    System.Security.Authentication.SslProtocols.Default, false);

                if (sslStream.IsAuthenticated)
                {
                    sslStream.Write(header_bytes, 0, header_bytes.Length);
                    sslStream.Flush();

                    if (_method.Equals("POST"))
                    {
                        byte[] data_bytes = _encoding.GetBytes(_data);
                        sslStream.Write(data_bytes, 0, data_bytes.Length);
                    }

                    // begin receive
                    MemoryStream recv_stream = new MemoryStream(10240);
                    int stream_length = 0;
                    int body_start = 0;

                    int read_length = 0;
                    // first read header, header cannot longer than 10240
                    int block_size = 10240;
                    byte[] recv = new byte[block_size];
                    read_length = sslStream.Read(recv, 0, recv.Length);

                    recv_stream.Write(recv, 0, read_length);
                    stream_length += read_length;
                    recv_stream.Seek(0, SeekOrigin.Begin);

                    processHeader(recv_stream, stream_length, ref body_start);

                    MemoryStream body_stream = new MemoryStream();
                    body_stream.Seek(0, SeekOrigin.Begin);
                    int body_now_length = stream_length - body_start;

                    if (_result.Chunked)
                    {
                        getChunkedData(ref sslStream, ref recv_stream, ref body_stream, ref body_start, stream_length);
                    }
                    else
                    {
                        if (stream_length - body_start > 0)
                        {
                            // 先把recv_stream里面已经读取到的数据写入body_stream
                            byte[] tmpbuffer = new byte[stream_length - body_start];
                            recv_stream.Read(tmpbuffer, 0, tmpbuffer.Length);
                            body_stream.Write(tmpbuffer, 0, tmpbuffer.Length);
                        }

                        // 已经知道数据长度
                        if (_result.ContentLength >= 0)
                        {
                            if (_result.ContentLength > body_now_length)
                            {
                                body_stream.Seek(0, SeekOrigin.End);
                                readSocketData(sslStream, ref body_stream, ref body_now_length, _result.ContentLength);
                            }
                        }
                        else
                        {
                            // 如果接收到的数据是满的(说明可能还有额外的数据) 或者 接收到的数据只有header
                            if (read_length >= block_size || (_result.StatusCode == 200 && body_start == stream_length))
                            {
                                body_stream.Seek(0, SeekOrigin.End);
                                readSocketData(sslStream, ref body_stream, ref body_now_length);
                            }
                        }
                    }
                    client.Close();
                    client = null;
                    processBody(body_stream, body_now_length);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(String.Format("ERROR::{0}::{1}", ex.Message, ex.StackTrace));
            }
            finally
            {
                if (client != null)
                {
                    client.Close();
                    client = null;
                }
            }
        }
        private void processHeader(MemoryStream data_stream, int now_data_length, ref int body_start)
        {
            byte[] total = new byte[now_data_length];
            data_stream.Read(total, 0, now_data_length);

            // 首先找出header, \r\n\r\n之前的就是header
            StringBuilder sbh = new StringBuilder(1024);
            for (int i = 0; i < now_data_length; i++)
            {
                char c = (char)total[i];
                sbh.Append(c);
                if (c == '\n' &&
                    string.Concat(sbh[sbh.Length - 4], sbh[sbh.Length - 3], sbh[sbh.Length - 2], sbh[sbh.Length - 1]).Contains("\r\n\r\n"))
                {
                    body_start = i + 1;
                    break;
                }
            }
            _result.Encoding = _encoding;
            string header_string = sbh.ToString();
            _result.parseHeader(header_string);
        }
        private void processBody(MemoryStream body_stream, int stream_length)
        {
            body_stream.Seek(0, SeekOrigin.Begin);
            _result.ContentStream = body_stream;
        }
        private string getHeaderString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(String.Format("{0} {1} HTTP/1.1\r\n", _method, _uri.PathAndQuery));
            sb.Append(String.Format("Host: {0}\r\n", _uri.Host));
            sb.Append(String.Format("User-Agent: {0}\r\n", _userAgent));
            sb.Append("Accept: */*\r\n");
            sb.Append("Accept-Encoding: gzip, deflate\r\n");
            sb.Append("Connection: keep-alive\r\n");
            if (_referer != null && _referer.Length > 0) sb.Append(String.Format("Referer: {0}\r\n", _referer));
            if (_cookies != null && _cookies.Count > 0)
            {
                string cook_str = "";
                List<Cookie> current_url_cookies = CommonUtils.getCookieByUrl(_cookies, _url);
                foreach (Cookie cook in current_url_cookies) cook_str += String.Format("{0}={1};", cook.Name, cook.Value);
                sb.Append(String.Format("Cookie: {0}\r\n", cook_str));
            }
            if (_extraHeaders != null && _extraHeaders.Count > 0)
            {
                IEnumerator<string> enumer = _extraHeaders.Keys.GetEnumerator();
                while (enumer.MoveNext())
                {
                    sb.Append(String.Format("{0}: {1}\r\n", enumer.Current, _extraHeaders[enumer.Current]));
                }
            }
            if (_method.Equals("POST"))
            {
                sb.Append("Content-Type: application/x-www-form-urlencoded\r\n");
                sb.Append(String.Format("Content-Length: {0}\r\n", _data.Length));
            }
            sb.Append("\r\n");
            return sb.ToString();
        }
    }
}
